﻿#region Copyright & License
/*
   Copyright 2009-2010 Stepan Adamec (adamec@yasas.org)

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License. 
 */
#endregion

namespace See.Sharper {
  using System;
  using System.IO;
  using System.Text;

  /// <summary>
  /// Extensions of frequently used classes in 'System.IO' namespace.
  /// </summary>
  public static class Sharper_IO {
    /// <summary>
    /// File name filter used as a default filter for selecting all files.
    /// </summary>
    public static readonly string ALL_FILES_PATTERN = "*.*";
    /// <summary>
    /// Size of the byte array used for copying stream content.
    /// </summary>
    public static readonly int DEFAULT_BUFFER_SIZE = 4096;

    #region Directories

    /// <summary>
    /// Creates the directory represented by DirectoryInfo instance, including any necessary but nonexistent parent directories. Note that if this operation fails it may have succeeded in creating some of the necessary parent directories.
    /// </summary>
    /// <param name="self"></param>
    public static void CreateFully(this DirectoryInfo self) {
      if (!self.Parent.Exists) {
        self.Parent.CreateFully();
      }

      self.Create();
    }

    /// <summary>
    /// Executes the <paramref name="action"/> for each file in the parent folder/directory.
    /// </summary>
    /// <param name="self">Parent folder/directory.</param>
    /// <param name="action">Action to be executed.</param>
    public static void EachFile(this DirectoryInfo self, Action<FileInfo> action) {
      self.EachFile(ALL_FILES_PATTERN, action);
    }

    /// <summary>
    /// Executes the <paramref name="action"/> for each file whose name matches the given pattern in the parent folder/directory.
    /// </summary>
    /// <param name="self">Parent folder/directory.</param>
    /// <param name="pattern">Widlcard pattern used as a file name filter.</param>
    /// <param name="action">Action to be executed.</param>
    /// <seealso cref="System.IO.DirectoryInfo.GetFiles(String)"/>
    public static void EachFile(this DirectoryInfo self, string pattern, Action<FileInfo> action) {
      self.GetFiles(pattern).Each<FileInfo>(file => action(file));
    }

    /// <summary>
    /// Executes the <paramref name="action"/> for each file in the parent folder/directory and all descendant folders/directories.
    /// Descendant directories are recursively searched in a depth-first fashion.
    /// </summary>
    /// <param name="self">Parent folder/directory.</param>
    /// <param name="action">Action to be executed.</param>
    public static void EachFileRecurse(this DirectoryInfo self, Action<FileInfo> action) {
      self.EachFileRecurse(ALL_FILES_PATTERN, action);
    }

    /// <summary>
    /// Executes the <paramref name="action"/> for each file whose name matches the given pattern in the parent folder/directory and all descendant folders/directories.
    /// Descendant directories are recursively searched in a depth-first fashion.
    /// </summary>
    /// <param name="self">Parent folder/directory.</param>
    /// <param name="pattern">Widlcard pattern used as a file name filter.</param>
    /// <param name="action">Action to be executed.</param>
    public static void EachFileRecurse(this DirectoryInfo self, string pattern, Action<FileInfo> action) {
      self.GetFiles(pattern, SearchOption.AllDirectories).Each<FileInfo>(file => action(file));
    }

    #endregion

    #region Files

    /// <summary>
    /// Traverse through each byte of this file and executes the <paramref name="action"/> on each byte.
    /// </summary>
    /// <param name="self">The file.</param>
    /// <param name="action">The action to be executed.</param>
    public static void EachByte(this FileInfo self, Action<byte> action) {
      new FileStream(self.FullName, FileMode.Open, FileAccess.Read).Using(
        it => new BufferedStream(it).Using(stream => {
          int b;

          do {
            b = stream.ReadByte();

            if (b != -1) {
              action((byte)b);
            }
          } while (b != -1);
        })
      );
    }

    /// <summary>
    /// Traverse through each line of this file and executes the <paramref name="action"/> on each line.
    /// </summary>
    /// <param name="self">The file.</param>
    /// <param name="action">The action to be executed.</param>
    public static void EachLine(this FileInfo self, Action<string> action) {
      string line = String.Empty;

      self.WithReader(reader => {
        do {
          line = reader.ReadLine();

          if (line != null) {
            action(line);
          }
        } while (line != null);
      });
    }

    /// <summary>
    /// Creates a new StreamReader for this file using default encoding and then passes it into the <paramref name="action"/>, ensuring the reader is closed after the action returns.
    /// </summary>
    /// <param name="self">The file.</param>
    /// <param name="action">Action to be executed.</param>
    public static void WithReader(this FileInfo self, Action<StreamReader> action) {
      self.WithReader(Encoding.Default, action);
    }

    /// <summary>
    /// Creates a new StreamReader for this file and then passes it into the <paramref name="action"/>, ensuring the reader is closed after the action returns.
    /// </summary>
    /// <param name="self">The file.</param>
    /// <param name="encoding">Encoding used to create the stream reader.</param>
    /// <param name="action">Action to be executed.</param>
    public static void WithReader(this FileInfo self, Encoding encoding, Action<StreamReader> action) {
      self.OpenRead().Using(stream => {
        try {
          new StreamReader(stream, encoding).Using(reader => {
            try {
              action(reader);
            } finally {
              reader.CloseQuietly();
            }
          });
        } finally {
          stream.CloseQuietly();
        }
      });
    }

    #endregion

    #region File System Objects

    /// <summary>
    /// Returns a relative path of a given file system object to the given <paramref name="parent"/>.
    /// </summary>
    /// <param name="self"></param>
    /// <param name="parent">Parent directory specified as FileInfo/DirectoryInfo instance.</param>
    /// <returns>Relative path to the given parent.</returns>
    public static string GetRelativeName(this FileSystemInfo self, FileSystemInfo parent) {
      if (parent == null) {
        throw new ArgumentNullException("parent");
      }

      return self.GetRelativeName(parent.FullName);
    }

    /// <summary>
    /// Returns a relative path of a given file system object to the given <paramref name="parent"/>.
    /// <example><code>
    /// System.Console.WriteLine(
    ///   new FileInfo(@"C:\Folder_1\Folder_2\Folder_3\File").GetRelativeName(@"C:\Folder_1")
    /// );
    /// Output: Folder_2\Folder_3\File
    /// </code></example>
    /// </summary>
    /// <param name="self">The file or directory.</param>
    /// <param name="parent">Parent directory specified as string.</param>
    /// <returns>Relative path to the given parent.</returns>
    public static string GetRelativeName(this FileSystemInfo self, string parent) {
      if (parent == null) {
        throw new ArgumentNullException("parent");
      }

      string path = parent;

      if (!path.EndsWith(Path.DirectorySeparatorChar.ToString())) {
        path = path + Path.DirectorySeparatorChar;
      }
      if (self.FullName.IndexOf(path) > -1) {
        return self.FullName.Substring(path.Length);
      }

      return self.FullName;
    }

    #endregion

    #region Streams

    /// <summary>
    /// Copies the content of the stream <paramref name="self"/> to another using default buffer size. Both source and target streams will be closed.
    /// </summary>
    /// <typeparam name="S">Type of the stream.</typeparam>
    /// <param name="self">The source stream.</param>
    /// <param name="target">The target stream</param>
    public static void CopyTo<S>(this S self, S target) where S : Stream {
      self.CopyTo(target, DEFAULT_BUFFER_SIZE, true, true);
    }

    /// <summary>
    /// Copies the content of the stream <paramref name="self"/> to another using the given buffer size. Both source and target streams will be closed.
    /// </summary>
    /// <typeparam name="S">Type of the stream.</typeparam>
    /// <param name="self">The source stream.</param>
    /// <param name="target">The target stream</param>
    /// <param name="bufferSize">Size of the buffer to be used.</param>
    public static void CopyTo<S>(this S self, S target, int bufferSize) where S : Stream {
      self.CopyTo(target, bufferSize, true, true);
    }

    /// <summary>
    /// Copies the content of the stream <paramref name="self"/> to another using default buffer size. Source and target streams will be closed depending on the value of a corresponding parameter.
    /// </summary>
    /// <typeparam name="S">Type of the stream.</typeparam>
    /// <param name="self">The source stream.</param>
    /// <param name="target">The target stream</param>
    /// <param name="closeSelf">Close the source stream <paramref name="self"/> after copying.</param>
    /// <param name="closeTarget">Close the <paramref name="target"/> stream after copying.</param>
    public static void CopyTo<S>(this S self, S target, bool closeSelf, bool closeTarget) where S : Stream {
      self.CopyTo(target, DEFAULT_BUFFER_SIZE, closeSelf, closeTarget);
    }

    /// <summary>
    /// Copies the content of the stream <paramref name="self"/> to another using the given buffer size. Source and target streams will be closed depending on the value of a corresponding parameter.
    /// </summary>
    /// <typeparam name="S">Type of the stream.</typeparam>
    /// <param name="self">The source stream.</param>
    /// <param name="target">The target stream</param>
    /// <param name="bufferSize">Size of the buffer to be used.</param>
    /// <param name="closeSelf">Close the source stream <paramref name="self"/> after copying.</param>
    /// <param name="closeTarget">Close the <paramref name="target"/> stream after copying.</param>
    public static void CopyTo<S>(this S self, S target, int bufferSize, bool closeSelf, bool closeTarget) where S : Stream {
      try {
        try {
          int bytes = 0;
          byte[] buffer = new byte[bufferSize];

          do {
            bytes = self.Read(
              buffer, 0, bufferSize
            );
            if (bytes > 0) {
              target.Write(buffer, 0, bytes);
              target.Flush();
            }
          } while (bytes > 0);
        } finally {
          if (closeSelf) self.Close();
        }
      } finally {
        if (closeTarget) target.Close();
      }
    }

    /// <summary>
    /// Closes the given stream <paramref name="self"/> without throwing any exceptions.
    /// </summary>
    /// <param name="self">Stream instance to be closed.</param>
    public static void CloseQuietly(this Stream self) {
      try {
        self.Close();
      } catch (Exception) {
        ;
      }
    }

    #endregion

    #region Readers

    /// <summary>
    /// Closes the given text reader <paramref name="self"/> without throwing any exceptions.
    /// </summary>
    /// <param name="self">TextReader instance to be closed.</param>
    public static void CloseQuietly(this TextReader self) {
      try {
        self.Close();
      } catch (Exception) {
        ;
      }
    }

    #endregion
  }
}
